package org.zh.sh.config;

import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.SimpleFileServer;
import org.zh.sh.util.ComponentScan;
import org.zh.sh.util.TrConsumer;
import org.zh.sh.util.Utils;
import org.zh.sh.util.create.SQLiteTablesInfo;
import org.zh.sh.util.http.ContextHttpHandler;
import org.zh.sh.util.http.Controller;
import org.zh.sh.util.http.PathBean;
import org.zh.sh.util.http.RequestMapping;
import org.zh.sh.util.jdbc.Transaction;
import org.zh.sh.util.jdbc.TransactionBean;
import org.zh.sh.util.proxy.Service;
import org.zh.sh.vll.*;

import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.file.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;

import static com.sun.net.httpserver.SimpleFileServer.OutputLevel.VERBOSE;
import static org.zh.sh.config.GlobalStatic.*;
import static org.zh.sh.util.ComponentScan.staticFileBytes;
import static org.zh.sh.util.create.TablesInfo.getConn;

@SuppressWarnings("ALL")
public class ZHOpenApplication {

    private ZHOpenApplication(){}


    public static void run(Class<?> mainClass, String[] args) {
        try {
            env.load(ZHOpenApplication.class.getClassLoader().getResourceAsStream("ZHOpen.properties"));
            Set<Object> vlls = new HashSet<>();
            vlls.add(new TeacherVLL());
            vlls.add(new StudentCurriculumVLL());
            vlls.add(new StudentVLL());
            vlls.add(new CurriculumVLL());
            vlls.add(new AppUserVLL());
            vlls.add(new AppRoleVLL());
            vlls.add(new AppUrlVLL());
            List<Class<?>> run = ComponentScan.componentScan(mainClass);
            for (Class<?> clazz : run) {
                controller.accept(clazz, vlls, vllCache);
                tra.accept(clazz, hashTra);
            }
            staticFileBytes();
            tableCacheInit();
            insertAppUrl();
            httpServerStart();
            print();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }


    private static void insertAppUrl() throws SQLException {
        HashSet<String> paths = new HashSet<>(vllCache.keySet());
        paths.addAll(staticFile.keySet());
        String sql = "insert into app_url (appUrlPath) values ";
        for (String path : paths) {
            sql += "(" + Utils.quote(path) + "),";
        }
        sql = sql.substring(0, sql.length() - 1);
        Connection conn = getConn(env.getProperty(zhOpenDbType));
        Statement st1 = conn.createStatement();
        st1.execute("delete from app_url");
        Statement st2 = conn.createStatement();
        st2.execute(sql);
        st1.close();
        st2.close();
        conn.close();
    }

    public static final TrConsumer<Class<?>, Set<Object>, Map<String, PathBean>> controller = (clazz, vlls, vllCache) -> {
        Controller vll = clazz.getAnnotation(Controller.class);
        if (vll != null) {
            String classPath = vll.value();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                RequestMapping mapping = method.getAnnotation(RequestMapping.class);
                if (mapping != null) {
                    Type[] types = method.getGenericParameterTypes();
                    String path = classPath + mapping.value();
                    String httpMethod = mapping.method().value();
                    String produces = mapping.produces();
                    String consumes = mapping.consumes();
                    MethodType methodType = MethodType.methodType(method.getReturnType(), method.getParameterTypes());
                    MethodHandle methodHandle = null;
                    for (Object obj : vlls) {
                        if (obj.getClass().equals(clazz)) {
                            methodHandle = lookup.findVirtual(clazz, method.getName(), methodType).bindTo(obj);
                        }
                    }
                    PathBean pathBean = new PathBean();
                    pathBean.setPath(path);
                    pathBean.setHttpMethod(httpMethod);
                    pathBean.setVllClass(clazz);
                    pathBean.setTypes(types);
                    if (methodHandle == null) {
                        throw new RuntimeException("vlls 傳參有無");
                    }
                    pathBean.setMethodHandle(methodHandle);
                    pathBean.setProduces(produces);
                    pathBean.setConsumes(consumes);
                    vllCache.put(path, pathBean);
                }
            }
        }
    };

    public static final BiConsumer<Class<?>, Map<Integer, TransactionBean>> tra = (clazz, hashTra) -> {
        Service service = clazz.getAnnotation(Service.class);
        if (service != null) {
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                Transaction tran = method.getAnnotation(Transaction.class);
                if (tran != null) {
                    TransactionBean tb = new TransactionBean(tran.readOnly(), tran.isolation().value());
                    putHashTra(method.hashCode(), tb);
                }
            }
        }
    };


    private static void tableCacheInit() throws SQLException {
        String property = env.getProperty(zhOpenTables);
        String[] tables = property.split(",");
        Connection conn = getConn(env.getProperty(zhOpenDbType));
        for (String table : tables) {
            List<String> list = SQLiteTablesInfo.info.tablePri(table, conn);
            if (list.isEmpty()) {
                tablePris.put(table, null);
            } else {
                tablePris.put(table, list.getFirst());
            }
        }
        conn.close();
    }



    private static void initDB(Path path1) {

        try {
            if (path1.endsWith(env.getProperty(zhOpenDbType) + ".sql")) {
                Connection conn = getConn(env.getProperty(zhOpenDbType));
                byte[] bytes = Files.readAllBytes(path1);
                String[] split = new String(bytes).split(";");
                for (String sql : split) {
                    Statement state = conn.createStatement();
                    state.execute(sql);
                    state.close();
                }
                conn.close();
            }
        } catch (IOException | SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private static void httpServerStart() throws IOException {
        HttpServer httpServer = HttpServer
                .create(new InetSocketAddress(Integer.parseInt(env.getProperty(zhOpenServerPort, "8080"))),
                        Integer.parseInt(env.getProperty(zhOpenServerBacklog, "128")),
                      "/",
                        new ContextHttpHandler(), Filter.afterHandler("close HttpExchange",exchange -> exchange.close()));
        httpServer.setExecutor(Executors.newFixedThreadPool(Integer.parseInt(env.getProperty(zhOpenServerFixedThread, "4"))));
        httpServer.start();
        HttpServer file = SimpleFileServer.createFileServer(new InetSocketAddress(8080),Paths.get("E:\\dianying"),VERBOSE);
        file.start();
        System.out.println("服务器启动成功：：  http://"+httpServer.getAddress().getAddress().getLocalHost().getHostAddress()+":"+httpServer.getAddress().getPort());
    }


}
